/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- *//* vim: set ts=8 sts=2 et sw=2 tw=80: *//* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */#include"mozilla/dom/KeyframeEffectReadOnly.h"#include"gfxPrefs.h"#include"mozilla/dom/KeyframeAnimationOptionsBinding.h"// For UnrestrictedDoubleOrKeyframeAnimationOptions;#include"mozilla/dom/CSSPseudoElement.h"#include"mozilla/dom/KeyframeEffectBinding.h"#include"mozilla/AnimValuesStyleRule.h"#include"mozilla/AnimationUtils.h"#include"mozilla/AutoRestore.h"#include"mozilla/EffectSet.h"#include"mozilla/GeckoStyleContext.h"#include"mozilla/FloatingPoint.h" // For IsFinite#include"mozilla/LookAndFeel.h" // For LookAndFeel::GetInt#include"mozilla/KeyframeUtils.h"#include"mozilla/ServoBindings.h"#include"mozilla/Telemetry.h"#include"mozilla/TypeTraits.h"#include"Layers.h" // For Layer#include"nsComputedDOMStyle.h" // nsComputedDOMStyle::GetStyleContext#include"nsCSSPropertyIDSet.h"#include"nsCSSProps.h" // For nsCSSProps::PropHasFlags#include"nsCSSPseudoElements.h" // For CSSPseudoElementType#include"nsIPresShell.h"#include"nsIScriptError.h"#include"nsStyleContextInlines.h"namespacemozilla{boolPropertyValuePair::operator==(constPropertyValuePair&aOther)const{if(mProperty!=aOther.mProperty||mValue!=aOther.mValue){returnfalse;}if(mServoDeclarationBlock==aOther.mServoDeclarationBlock){returntrue;}if(!mServoDeclarationBlock||!aOther.mServoDeclarationBlock){returnfalse;}returnServo_DeclarationBlock_Equals(mServoDeclarationBlock,aOther.mServoDeclarationBlock);}namespacedom{NS_IMPL_CYCLE_COLLECTION_INHERITED(KeyframeEffectReadOnly,AnimationEffectReadOnly,mTarget)NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(KeyframeEffectReadOnly,AnimationEffectReadOnly)NS_IMPL_CYCLE_COLLECTION_TRACE_ENDNS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(KeyframeEffectReadOnly)NS_INTERFACE_MAP_END_INHERITING(AnimationEffectReadOnly)NS_IMPL_ADDREF_INHERITED(KeyframeEffectReadOnly,AnimationEffectReadOnly)NS_IMPL_RELEASE_INHERITED(KeyframeEffectReadOnly,AnimationEffectReadOnly)KeyframeEffectReadOnly::KeyframeEffectReadOnly(nsIDocument*aDocument,constMaybe<OwningAnimationTarget>&aTarget,constTimingParams&aTiming,constKeyframeEffectParams&aOptions):KeyframeEffectReadOnly(aDocument,aTarget,newAnimationEffectTimingReadOnly(aDocument,aTiming),aOptions){}KeyframeEffectReadOnly::KeyframeEffectReadOnly(nsIDocument*aDocument,constMaybe<OwningAnimationTarget>&aTarget,AnimationEffectTimingReadOnly*aTiming,constKeyframeEffectParams&aOptions):AnimationEffectReadOnly(aDocument,aTiming),mTarget(aTarget),mEffectOptions(aOptions),mInEffectOnLastAnimationTimingUpdate(false),mCumulativeChangeHint(nsChangeHint(0)){}JSObject*KeyframeEffectReadOnly::WrapObject(JSContext*aCx,JS::Handle<JSObject*>aGivenProto){returnKeyframeEffectReadOnlyBinding::Wrap(aCx,this,aGivenProto);}IterationCompositeOperationKeyframeEffectReadOnly::IterationComposite()const{returnmEffectOptions.mIterationComposite;}CompositeOperationKeyframeEffectReadOnly::Composite()const{returnmEffectOptions.mComposite;}voidKeyframeEffectReadOnly::NotifyAnimationTimingUpdated(){UpdateTargetRegistration();// If the effect is not relevant it will be removed from the target// element's effect set. However, effects not in the effect set// will not be included in the set of candidate effects for running on// the compositor and hence they won't have their compositor status// updated. As a result, we need to make sure we clear their compositor// status here.boolisRelevant=mAnimation&&mAnimation->IsRelevant();if(!isRelevant){ResetIsRunningOnCompositor();}// Detect changes to "in effect" status since we need to recalculate the// animation cascade for this element whenever that changes.boolinEffect=IsInEffect();if(inEffect!=mInEffectOnLastAnimationTimingUpdate){MarkCascadeNeedsUpdate();mInEffectOnLastAnimationTimingUpdate=inEffect;}// Request restyle if necessary.if(mAnimation&&!mProperties.IsEmpty()&&HasComputedTimingChanged()){EffectCompositor::RestyleTyperestyleType=CanThrottle()?EffectCompositor::RestyleType::Throttled:EffectCompositor::RestyleType::Standard;RequestRestyle(restyleType);}// If we're no longer "in effect", our ComposeStyle method will never be// called and we will never have a chance to update mProgressOnLastCompose// and mCurrentIterationOnLastCompose.// We clear them here to ensure that if we later become "in effect" we will// request a restyle (above).if(!inEffect){mProgressOnLastCompose.SetNull();mCurrentIterationOnLastCompose=0;}}staticboolKeyframesEqualIgnoringComputedOffsets(constnsTArray<Keyframe>&aLhs,constnsTArray<Keyframe>&aRhs){if(aLhs.Length()!=aRhs.Length()){returnfalse;}for(size_ti=0,len=aLhs.Length();i<len;++i){constKeyframe&a=aLhs[i];constKeyframe&b=aRhs[i];if(a.mOffset!=b.mOffset||a.mTimingFunction!=b.mTimingFunction||a.mPropertyValues!=b.mPropertyValues){returnfalse;}}returntrue;}// https://w3c.github.io/web-animations/#dom-keyframeeffect-setkeyframesvoidKeyframeEffectReadOnly::SetKeyframes(JSContext*aContext,JS::Handle<JSObject*>aKeyframes,ErrorResult&aRv){nsTArray<Keyframe>keyframes=KeyframeUtils::GetKeyframesFromObject(aContext,mDocument,aKeyframes,aRv);if(aRv.Failed()){return;}RefPtr<nsStyleContext>styleContext=GetTargetStyleContext();SetKeyframes(Move(keyframes),styleContext);}voidKeyframeEffectReadOnly::SetKeyframes(nsTArray<Keyframe>&&aKeyframes,nsStyleContext*aStyleContext){DoSetKeyframes(Move(aKeyframes),Move(aStyleContext));}voidKeyframeEffectReadOnly::SetKeyframes(nsTArray<Keyframe>&&aKeyframes,constServoComputedValues*aComputedValues){DoSetKeyframes(Move(aKeyframes),aComputedValues);}template<typenameStyleType>voidKeyframeEffectReadOnly::DoSetKeyframes(nsTArray<Keyframe>&&aKeyframes,StyleType*aStyle){static_assert(IsSame<StyleType,nsStyleContext>::value||IsSame<StyleType,constServoComputedValues>::value,"StyleType should be nsStyleContext* or ""const ServoComputedValues*");if(KeyframesEqualIgnoringComputedOffsets(aKeyframes,mKeyframes)){return;}mKeyframes=Move(aKeyframes);KeyframeUtils::DistributeKeyframes(mKeyframes);if(mAnimation&&mAnimation->IsRelevant()){nsNodeUtils::AnimationChanged(mAnimation);}// We need to call UpdateProperties() if the StyleType is not nullptr.if(aStyle){UpdateProperties(aStyle);MaybeUpdateFrameForCompositor();}}constAnimationProperty*KeyframeEffectReadOnly::GetEffectiveAnimationOfProperty(nsCSSPropertyIDaProperty)const{EffectSet*effectSet=EffectSet::GetEffectSet(mTarget->mElement,mTarget->mPseudoType);for(size_tpropIdx=0,propEnd=mProperties.Length();propIdx!=propEnd;++propIdx){if(aProperty==mProperties[propIdx].mProperty){constAnimationProperty*result=&mProperties[propIdx];// Skip if there is a property of animation level that is overridden// by !important rules.if(effectSet&&effectSet->PropertiesWithImportantRules().HasProperty(result->mProperty)&&effectSet->PropertiesForAnimationsLevel().HasProperty(result->mProperty)){result=nullptr;}returnresult;}}returnnullptr;}boolKeyframeEffectReadOnly::HasAnimationOfProperty(nsCSSPropertyIDaProperty)const{for(constAnimationProperty&property:mProperties){if(property.mProperty==aProperty){returntrue;}}returnfalse;}#ifdef DEBUGboolSpecifiedKeyframeArraysAreEqual(constnsTArray<Keyframe>&aA,constnsTArray<Keyframe>&aB){if(aA.Length()!=aB.Length()){returnfalse;}for(size_ti=0;i<aA.Length();i++){constKeyframe&a=aA[i];constKeyframe&b=aB[i];if(a.mOffset!=b.mOffset||a.mTimingFunction!=b.mTimingFunction||a.mPropertyValues!=b.mPropertyValues){returnfalse;}}returntrue;}#endifvoidKeyframeEffectReadOnly::UpdateProperties(nsStyleContext*aStyleContext){MOZ_ASSERT(aStyleContext);if(!mDocument->IsStyledByServo()){DoUpdateProperties(Move(aStyleContext));return;}constServoComputedValues*currentStyle=aStyleContext->ComputedValues();DoUpdateProperties(currentStyle);}voidKeyframeEffectReadOnly::UpdateProperties(constServoComputedValues*aComputedValues){DoUpdateProperties(aComputedValues);}template<typenameStyleType>voidKeyframeEffectReadOnly::DoUpdateProperties(StyleType*aStyle){MOZ_ASSERT(aStyle);// Skip updating properties when we are composing style.// FIXME: Bug 1324966. Drop this check once we have a function to get// nsStyleContext without resolving animating style.MOZ_DIAGNOSTIC_ASSERT(!mIsComposingStyle,"Should not be called while processing ComposeStyle()");if(mIsComposingStyle){return;}nsTArray<AnimationProperty>properties=BuildProperties(aStyle);// We need to update base styles even if any properties are not changed at all// since base styles might have been changed due to parent style changes, etc.EnsureBaseStyles(aStyle,properties);if(mProperties==properties){return;}// Preserve the state of the mIsRunningOnCompositor flag.nsCSSPropertyIDSetrunningOnCompositorProperties;for(constAnimationProperty&property:mProperties){if(property.mIsRunningOnCompositor){runningOnCompositorProperties.AddProperty(property.mProperty);}}mProperties=Move(properties);UpdateEffectSet();for(AnimationProperty&property:mProperties){property.mIsRunningOnCompositor=runningOnCompositorProperties.HasProperty(property.mProperty);}CalculateCumulativeChangeHint(aStyle);MarkCascadeNeedsUpdate();RequestRestyle(EffectCompositor::RestyleType::Layer);}/* static */StyleAnimationValueKeyframeEffectReadOnly::CompositeValue(nsCSSPropertyIDaProperty,constStyleAnimationValue&aValueToComposite,constStyleAnimationValue&aUnderlyingValue,CompositeOperationaCompositeOperation){// Just return the underlying value if |aValueToComposite| is null// (i.e. missing keyframe).if(aValueToComposite.IsNull()){returnaUnderlyingValue;}switch(aCompositeOperation){casedom::CompositeOperation::Replace:returnaValueToComposite;casedom::CompositeOperation::Add:{StyleAnimationValueresult(aValueToComposite);returnStyleAnimationValue::Add(aProperty,aUnderlyingValue,Move(result));}casedom::CompositeOperation::Accumulate:{StyleAnimationValueresult(aValueToComposite);returnStyleAnimationValue::Accumulate(aProperty,aUnderlyingValue,Move(result));}default:MOZ_ASSERT_UNREACHABLE("Unknown compisite operation type");break;}returnStyleAnimationValue();}StyleAnimationValueKeyframeEffectReadOnly::GetUnderlyingStyle(nsCSSPropertyIDaProperty,constRefPtr<AnimValuesStyleRule>&aAnimationRule){StyleAnimationValueresult;if(aAnimationRule&&aAnimationRule->HasValue(aProperty)){// If we have already composed style for the property, we use the style// as the underlying style.DebugOnly<bool>success=aAnimationRule->GetValue(aProperty,result);MOZ_ASSERT(success,"AnimValuesStyleRule::GetValue should not fail");}else{// If we are composing with composite operation that is not 'replace'// and we have not composed style for the property yet, we have to get// the base style for the property.result=BaseStyle(aProperty).mGecko;}returnresult;}StyleAnimationValueKeyframeEffectReadOnly::CompositeValue(nsCSSPropertyIDaProperty,constRefPtr<AnimValuesStyleRule>&aAnimationRule,constStyleAnimationValue&aValueToComposite,CompositeOperationaCompositeOperation){MOZ_ASSERT(mTarget,"CompositeValue should be called with target element");MOZ_ASSERT(!mDocument->IsStyledByServo());StyleAnimationValueunderlyingValue=GetUnderlyingStyle(aProperty,aAnimationRule);returnCompositeValue(aProperty,aValueToComposite,underlyingValue,aCompositeOperation);}voidKeyframeEffectReadOnly::EnsureBaseStyles(nsStyleContext*aStyleContext,constnsTArray<AnimationProperty>&aProperties){if(!mTarget){return;}mBaseStyleValues.Clear();RefPtr<nsStyleContext>cachedBaseStyleContext;for(constAnimationProperty&property:aProperties){for(constAnimationPropertySegment&segment:property.mSegments){if(segment.HasReplaceableValues()){continue;}EnsureBaseStyle(property.mProperty,aStyleContext,cachedBaseStyleContext);break;}}}voidKeyframeEffectReadOnly::EnsureBaseStyle(nsCSSPropertyIDaProperty,nsStyleContext*aStyleContext,RefPtr<nsStyleContext>&aCachedBaseStyleContext){if(mBaseStyleValues.Contains(aProperty)){return;}if(!aCachedBaseStyleContext){aCachedBaseStyleContext=aStyleContext->PresContext()->StyleSet()->AsGecko()->ResolveStyleByRemovingAnimation(mTarget->mElement,aStyleContext,eRestyle_AllHintsWithAnimations);}StyleAnimationValueresult;DebugOnly<bool>success=StyleAnimationValue::ExtractComputedValue(aProperty,aCachedBaseStyleContext,result);MOZ_ASSERT(success,"Should be able to extract computed animation value");MOZ_ASSERT(!result.IsNull(),"Should have a valid StyleAnimationValue");mBaseStyleValues.Put(aProperty,result);}voidKeyframeEffectReadOnly::EnsureBaseStyles(constServoComputedValues*aComputedValues,constnsTArray<AnimationProperty>&aProperties){if(!mTarget){return;}mBaseStyleValuesForServo.Clear();nsPresContext*presContext=nsContentUtils::GetContextForContent(mTarget->mElement);MOZ_ASSERT(presContext,"nsPresContext should not be nullptr since this EnsureBaseStyles ""supposed to be called right after getting computed values with ""a valid nsPresContext");RefPtr<ServoComputedValues>baseComputedValues;for(constAnimationProperty&property:aProperties){EnsureBaseStyle(property,mTarget->mPseudoType,presContext,baseComputedValues);}}voidKeyframeEffectReadOnly::EnsureBaseStyle(constAnimationProperty&aProperty,CSSPseudoElementTypeaPseudoType,nsPresContext*aPresContext,RefPtr<ServoComputedValues>&aBaseComputedValues){boolhasAdditiveValues=false;for(constAnimationPropertySegment&segment:aProperty.mSegments){if(!segment.HasReplaceableValues()){hasAdditiveValues=true;break;}}if(!hasAdditiveValues){return;}if(!aBaseComputedValues){aBaseComputedValues=aPresContext->StyleSet()->AsServo()->GetBaseComputedValuesForElement(mTarget->mElement,aPseudoType);}RefPtr<RawServoAnimationValue>baseValue=Servo_ComputedValues_ExtractAnimationValue(aBaseComputedValues,aProperty.mProperty).Consume();mBaseStyleValuesForServo.Put(aProperty.mProperty,baseValue);}voidKeyframeEffectReadOnly::WillComposeStyle(){ComputedTimingcomputedTiming=GetComputedTiming();mProgressOnLastCompose=computedTiming.mProgress;mCurrentIterationOnLastCompose=computedTiming.mCurrentIteration;}voidKeyframeEffectReadOnly::ComposeStyleRule(RefPtr<AnimValuesStyleRule>&aStyleRule,constAnimationProperty&aProperty,constAnimationPropertySegment&aSegment,constComputedTiming&aComputedTiming){StyleAnimationValuefromValue=CompositeValue(aProperty.mProperty,aStyleRule,aSegment.mFromValue.mGecko,aSegment.mFromComposite);StyleAnimationValuetoValue=CompositeValue(aProperty.mProperty,aStyleRule,aSegment.mToValue.mGecko,aSegment.mToComposite);if(fromValue.IsNull()||toValue.IsNull()){return;}if(!aStyleRule){// Allocate the style rule now that we know we have animation data.aStyleRule=newAnimValuesStyleRule();}// Iteration composition for accumulateif(mEffectOptions.mIterationComposite==IterationCompositeOperation::Accumulate&&aComputedTiming.mCurrentIteration>0){constAnimationPropertySegment&lastSegment=aProperty.mSegments.LastElement();// FIXME: Bug 1293492: Add a utility function to calculate both of// below StyleAnimationValues.StyleAnimationValuelastValue=lastSegment.mToValue.mGecko.IsNull()?GetUnderlyingStyle(aProperty.mProperty,aStyleRule):lastSegment.mToValue.mGecko;fromValue=StyleAnimationValue::Accumulate(aProperty.mProperty,lastValue,Move(fromValue),aComputedTiming.mCurrentIteration);toValue=StyleAnimationValue::Accumulate(aProperty.mProperty,lastValue,Move(toValue),aComputedTiming.mCurrentIteration);}// Special handling for zero-length segmentsif(aSegment.mToKey==aSegment.mFromKey){if(aComputedTiming.mProgress.Value()<0){aStyleRule->AddValue(aProperty.mProperty,Move(fromValue));}else{aStyleRule->AddValue(aProperty.mProperty,Move(toValue));}return;}doublepositionInSegment=(aComputedTiming.mProgress.Value()-aSegment.mFromKey)/(aSegment.mToKey-aSegment.mFromKey);doublevaluePosition=ComputedTimingFunction::GetPortion(aSegment.mTimingFunction,positionInSegment,aComputedTiming.mBeforeFlag);MOZ_ASSERT(IsFinite(valuePosition),"Position value should be finite");StyleAnimationValueval;if(StyleAnimationValue::Interpolate(aProperty.mProperty,fromValue,toValue,valuePosition,val)){aStyleRule->AddValue(aProperty.mProperty,Move(val));}elseif(valuePosition<0.5){aStyleRule->AddValue(aProperty.mProperty,Move(fromValue));}else{aStyleRule->AddValue(aProperty.mProperty,Move(toValue));}}voidKeyframeEffectReadOnly::ComposeStyleRule(RawServoAnimationValueMap&aAnimationValues,constAnimationProperty&aProperty,constAnimationPropertySegment&aSegment,constComputedTiming&aComputedTiming){Servo_AnimationCompose(&aAnimationValues,&mBaseStyleValuesForServo,aProperty.mProperty,&aSegment,&aProperty.mSegments.LastElement(),&aComputedTiming,mEffectOptions.mIterationComposite);}template<typenameComposeAnimationResult>voidKeyframeEffectReadOnly::ComposeStyle(ComposeAnimationResult&&aComposeResult,constnsCSSPropertyIDSet&aPropertiesToSkip){MOZ_DIAGNOSTIC_ASSERT(!mIsComposingStyle,"Should not be called recursively");if(mIsComposingStyle){return;}AutoRestore<bool>isComposingStyle(mIsComposingStyle);mIsComposingStyle=true;ComputedTimingcomputedTiming=GetComputedTiming();// If the progress is null, we don't have fill data for the current// time so we shouldn't animate.if(computedTiming.mProgress.IsNull()){return;}for(size_tpropIdx=0,propEnd=mProperties.Length();propIdx!=propEnd;++propIdx){constAnimationProperty&prop=mProperties[propIdx];MOZ_ASSERT(prop.mSegments[0].mFromKey==0.0,"incorrect first from key");MOZ_ASSERT(prop.mSegments[prop.mSegments.Length()-1].mToKey==1.0,"incorrect last to key");if(aPropertiesToSkip.HasProperty(prop.mProperty)){continue;}MOZ_ASSERT(prop.mSegments.Length()>0,"property should not be in animations if it has no segments");// FIXME: Maybe cache the current segment?constAnimationPropertySegment*segment=prop.mSegments.Elements(),*segmentEnd=segment+prop.mSegments.Length();while(segment->mToKey<=computedTiming.mProgress.Value()){MOZ_ASSERT(segment->mFromKey<=segment->mToKey,"incorrect keys");if((segment+1)==segmentEnd){break;}++segment;MOZ_ASSERT(segment->mFromKey==(segment-1)->mToKey,"incorrect keys");}MOZ_ASSERT(segment->mFromKey<=segment->mToKey,"incorrect keys");MOZ_ASSERT(segment>=prop.mSegments.Elements()&&size_t(segment-prop.mSegments.Elements())<prop.mSegments.Length(),"out of array bounds");ComposeStyleRule(Forward<ComposeAnimationResult>(aComposeResult),prop,*segment,computedTiming);}}boolKeyframeEffectReadOnly::IsRunningOnCompositor()const{// We consider animation is running on compositor if there is at least// one property running on compositor.// Animation.IsRunningOnCompotitor will return more fine grained// information in bug 1196114.for(constAnimationProperty&property:mProperties){if(property.mIsRunningOnCompositor){returntrue;}}returnfalse;}voidKeyframeEffectReadOnly::SetIsRunningOnCompositor(nsCSSPropertyIDaProperty,boolaIsRunning){MOZ_ASSERT(nsCSSProps::PropHasFlags(aProperty,CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR),"Property being animated on compositor is a recognized ""compositor-animatable property");for(AnimationProperty&property:mProperties){if(property.mProperty==aProperty){property.mIsRunningOnCompositor=aIsRunning;// We currently only set a performance warning message when animations// cannot be run on the compositor, so if this animation is running// on the compositor we don't need a message.if(aIsRunning){property.mPerformanceWarning.reset();}return;}}}voidKeyframeEffectReadOnly::ResetIsRunningOnCompositor(){for(AnimationProperty&property:mProperties){property.mIsRunningOnCompositor=false;}}staticconstKeyframeEffectOptions&KeyframeEffectOptionsFromUnion(constUnrestrictedDoubleOrKeyframeEffectOptions&aOptions){MOZ_ASSERT(aOptions.IsKeyframeEffectOptions());returnaOptions.GetAsKeyframeEffectOptions();}staticconstKeyframeEffectOptions&KeyframeEffectOptionsFromUnion(constUnrestrictedDoubleOrKeyframeAnimationOptions&aOptions){MOZ_ASSERT(aOptions.IsKeyframeAnimationOptions());returnaOptions.GetAsKeyframeAnimationOptions();}template<classOptionsType>staticKeyframeEffectParamsKeyframeEffectParamsFromUnion(constOptionsType&aOptions,CallerTypeaCallerType){KeyframeEffectParamsresult;if(aOptions.IsUnrestrictedDouble()||// Ignore iterationComposite if the Web Animations API is not enabled,// then the default value 'Replace' will be used.!AnimationUtils::IsCoreAPIEnabledForCaller(aCallerType)){returnresult;}constKeyframeEffectOptions&options=KeyframeEffectOptionsFromUnion(aOptions);result.mIterationComposite=options.mIterationComposite;result.mComposite=options.mComposite;returnresult;}/* static */Maybe<OwningAnimationTarget>KeyframeEffectReadOnly::ConvertTarget(constNullable<ElementOrCSSPseudoElement>&aTarget){// Return value optimization.Maybe<OwningAnimationTarget>result;if(aTarget.IsNull()){returnresult;}constElementOrCSSPseudoElement&target=aTarget.Value();MOZ_ASSERT(target.IsElement()||target.IsCSSPseudoElement(),"Uninitialized target");if(target.IsElement()){result.emplace(&target.GetAsElement());}else{RefPtr<Element>elem=target.GetAsCSSPseudoElement().ParentElement();result.emplace(elem,target.GetAsCSSPseudoElement().GetType());}returnresult;}template<classKeyframeEffectType,classOptionsType>/* static */already_AddRefed<KeyframeEffectType>KeyframeEffectReadOnly::ConstructKeyframeEffect(constGlobalObject&aGlobal,constNullable<ElementOrCSSPseudoElement>&aTarget,JS::Handle<JSObject*>aKeyframes,constOptionsType&aOptions,ErrorResult&aRv){nsIDocument*doc=AnimationUtils::GetCurrentRealmDocument(aGlobal.Context());if(!doc){aRv.Throw(NS_ERROR_FAILURE);returnnullptr;}TimingParamstimingParams=TimingParams::FromOptionsUnion(aOptions,doc,aRv);if(aRv.Failed()){returnnullptr;}KeyframeEffectParamseffectOptions=KeyframeEffectParamsFromUnion(aOptions,aGlobal.CallerType());Maybe<OwningAnimationTarget>target=ConvertTarget(aTarget);RefPtr<KeyframeEffectType>effect=newKeyframeEffectType(doc,target,timingParams,effectOptions);effect->SetKeyframes(aGlobal.Context(),aKeyframes,aRv);if(aRv.Failed()){returnnullptr;}returneffect.forget();}template<classKeyframeEffectType>/* static */already_AddRefed<KeyframeEffectType>KeyframeEffectReadOnly::ConstructKeyframeEffect(constGlobalObject&aGlobal,KeyframeEffectReadOnly&aSource,ErrorResult&aRv){nsIDocument*doc=AnimationUtils::GetCurrentRealmDocument(aGlobal.Context());if(!doc){aRv.Throw(NS_ERROR_FAILURE);returnnullptr;}// Create a new KeyframeEffectReadOnly object with aSource's target,// iteration composite operation, composite operation, and spacing mode.// The constructor creates a new AnimationEffect(ReadOnly) object by// aSource's TimingParams.// Note: we don't need to re-throw exceptions since the value specified on// aSource's timing object can be assumed valid.RefPtr<KeyframeEffectType>effect=newKeyframeEffectType(doc,aSource.mTarget,aSource.SpecifiedTiming(),aSource.mEffectOptions);// Copy cumulative change hint. mCumulativeChangeHint should be the same as// the source one because both of targets are the same.effect->mCumulativeChangeHint=aSource.mCumulativeChangeHint;// Copy aSource's keyframes and animation properties.// Note: We don't call SetKeyframes directly, which might revise the// computed offsets and rebuild the animation properties.// FIXME: Bug 1314537: We have to make sure SharedKeyframeList is handled// properly.effect->mKeyframes=aSource.mKeyframes;effect->mProperties=aSource.mProperties;returneffect.forget();}template<typenameStyleType>nsTArray<AnimationProperty>KeyframeEffectReadOnly::BuildProperties(StyleType*aStyle){static_assert(IsSame<StyleType,nsStyleContext>::value||IsSame<StyleType,constServoComputedValues>::value,"StyleType should be nsStyleContext* or ""const ServoComputedValues*");MOZ_ASSERT(aStyle);nsTArray<AnimationProperty>result;// If mTarget is null, return an empty property array.if(!mTarget){returnresult;}// When GetComputedKeyframeValues or GetAnimationPropertiesFromKeyframes// calculate computed values from |mKeyframes|, they could possibly// trigger a subsequent restyle in which we rebuild animations. If that// happens we could find that |mKeyframes| is overwritten while it is// being iterated over. Normally that shouldn't happen but just in case we// make a copy of |mKeyframes| first and iterate over that instead.autokeyframesCopy(mKeyframes);result=KeyframeUtils::GetAnimationPropertiesFromKeyframes(keyframesCopy,mTarget->mElement,aStyle,mEffectOptions.mComposite);#ifdef DEBUGMOZ_ASSERT(SpecifiedKeyframeArraysAreEqual(mKeyframes,keyframesCopy),"Apart from the computed offset members, the keyframes array"" should not be modified");#endifmKeyframes.SwapElements(keyframesCopy);returnresult;}voidKeyframeEffectReadOnly::UpdateTargetRegistration(){if(!mTarget){return;}boolisRelevant=mAnimation&&mAnimation->IsRelevant();// Animation::IsRelevant() returns a cached value. It only updates when// something calls Animation::UpdateRelevance. Whenever our timing changes,// we should be notifying our Animation before calling this, so// Animation::IsRelevant() should be up-to-date by the time we get here.MOZ_ASSERT(isRelevant==IsCurrent()||IsInEffect(),"Out of date Animation::IsRelevant value");if(isRelevant){EffectSet*effectSet=EffectSet::GetOrCreateEffectSet(mTarget->mElement,mTarget->mPseudoType);effectSet->AddEffect(*this);UpdateEffectSet(effectSet);}else{UnregisterTarget();}}voidKeyframeEffectReadOnly::UnregisterTarget(){EffectSet*effectSet=EffectSet::GetEffectSet(mTarget->mElement,mTarget->mPseudoType);if(effectSet){effectSet->RemoveEffect(*this);if(effectSet->IsEmpty()){EffectSet::DestroyEffectSet(mTarget->mElement,mTarget->mPseudoType);}}}voidKeyframeEffectReadOnly::RequestRestyle(EffectCompositor::RestyleTypeaRestyleType){if(!mTarget){return;}nsPresContext*presContext=nsContentUtils::GetContextForContent(mTarget->mElement);if(presContext&&mAnimation){presContext->EffectCompositor()->RequestRestyle(mTarget->mElement,mTarget->mPseudoType,aRestyleType,mAnimation->CascadeLevel());}}already_AddRefed<nsStyleContext>KeyframeEffectReadOnly::GetTargetStyleContext(){nsIPresShell*shell=GetPresShell();if(!shell){returnnullptr;}MOZ_ASSERT(mTarget,"Should only have a presshell when we have a target element");nsIAtom*pseudo=mTarget->mPseudoType<CSSPseudoElementType::Count?nsCSSPseudoElements::GetPseudoAtom(mTarget->mPseudoType):nullptr;returnnsComputedDOMStyle::GetStyleContext(mTarget->mElement,pseudo,shell);}#ifdef DEBUGvoidDumpAnimationProperties(nsTArray<AnimationProperty>&aAnimationProperties){for(auto&p:aAnimationProperties){printf("%s\n",nsCSSProps::GetStringValue(p.mProperty).get());for(auto&s:p.mSegments){nsStringfromValue,toValue;s.mFromValue.SerializeSpecifiedValue(p.mProperty,fromValue);s.mToValue.SerializeSpecifiedValue(p.mProperty,toValue);printf(" %f..%f: %s..%s\n",s.mFromKey,s.mToKey,NS_ConvertUTF16toUTF8(fromValue).get(),NS_ConvertUTF16toUTF8(toValue).get());}}}#endif/* static */already_AddRefed<KeyframeEffectReadOnly>KeyframeEffectReadOnly::Constructor(constGlobalObject&aGlobal,constNullable<ElementOrCSSPseudoElement>&aTarget,JS::Handle<JSObject*>aKeyframes,constUnrestrictedDoubleOrKeyframeEffectOptions&aOptions,ErrorResult&aRv){returnConstructKeyframeEffect<KeyframeEffectReadOnly>(aGlobal,aTarget,aKeyframes,aOptions,aRv);}/* static */already_AddRefed<KeyframeEffectReadOnly>KeyframeEffectReadOnly::Constructor(constGlobalObject&aGlobal,KeyframeEffectReadOnly&aSource,ErrorResult&aRv){returnConstructKeyframeEffect<KeyframeEffectReadOnly>(aGlobal,aSource,aRv);}voidKeyframeEffectReadOnly::GetTarget(Nullable<OwningElementOrCSSPseudoElement>&aRv)const{if(!mTarget){aRv.SetNull();return;}switch(mTarget->mPseudoType){caseCSSPseudoElementType::before:caseCSSPseudoElementType::after:aRv.SetValue().SetAsCSSPseudoElement()=CSSPseudoElement::GetCSSPseudoElement(mTarget->mElement,mTarget->mPseudoType);break;caseCSSPseudoElementType::NotPseudo:aRv.SetValue().SetAsElement()=mTarget->mElement;break;default:NS_NOTREACHED("Animation of unsupported pseudo-type");aRv.SetNull();}}staticvoidCreatePropertyValue(nsCSSPropertyIDaProperty,floataOffset,constMaybe<ComputedTimingFunction>&aTimingFunction,constAnimationValue&aValue,dom::CompositeOperationaComposite,AnimationPropertyValueDetails&aResult){aResult.mOffset=aOffset;if(!aValue.IsNull()){nsStringstringValue;aValue.SerializeSpecifiedValue(aProperty,stringValue);aResult.mValue.Construct(stringValue);}if(aTimingFunction){aResult.mEasing.Construct();aTimingFunction->AppendToString(aResult.mEasing.Value());}else{aResult.mEasing.Construct(NS_LITERAL_STRING("linear"));}aResult.mComposite=aComposite;}voidKeyframeEffectReadOnly::GetProperties(nsTArray<AnimationPropertyDetails>&aProperties,ErrorResult&aRv)const{for(constAnimationProperty&property:mProperties){AnimationPropertyDetailspropertyDetails;propertyDetails.mProperty=NS_ConvertASCIItoUTF16(nsCSSProps::GetStringValue(property.mProperty));propertyDetails.mRunningOnCompositor=property.mIsRunningOnCompositor;nsXPIDLStringlocalizedString;if(property.mPerformanceWarning&&property.mPerformanceWarning->ToLocalizedString(localizedString)){propertyDetails.mWarning.Construct(localizedString);}if(!propertyDetails.mValues.SetCapacity(property.mSegments.Length(),mozilla::fallible)){aRv.Throw(NS_ERROR_OUT_OF_MEMORY);return;}for(size_tsegmentIdx=0,segmentLen=property.mSegments.Length();segmentIdx<segmentLen;segmentIdx++){constAnimationPropertySegment&segment=property.mSegments[segmentIdx];binding_detail::FastAnimationPropertyValueDetailsfromValue;CreatePropertyValue(property.mProperty,segment.mFromKey,segment.mTimingFunction,segment.mFromValue,segment.mFromComposite,fromValue);// We don't apply timing functions for zero-length segments, so// don't return one here.if(segment.mFromKey==segment.mToKey){fromValue.mEasing.Reset();}// The following won't fail since we have already allocated the capacity// above.propertyDetails.mValues.AppendElement(fromValue,mozilla::fallible);// Normally we can ignore the to-value for this segment since it is// identical to the from-value from the next segment. However, we need// to add it if either:// a) this is the last segment, or// b) the next segment's from-value differs.if(segmentIdx==segmentLen-1||property.mSegments[segmentIdx+1].mFromValue.mGecko!=segment.mToValue.mGecko){binding_detail::FastAnimationPropertyValueDetailstoValue;CreatePropertyValue(property.mProperty,segment.mToKey,Nothing(),segment.mToValue,segment.mToComposite,toValue);// It doesn't really make sense to have a timing function on the// last property value or before a sudden jump so we just drop the// easing property altogether.toValue.mEasing.Reset();propertyDetails.mValues.AppendElement(toValue,mozilla::fallible);}}aProperties.AppendElement(propertyDetails);}}voidKeyframeEffectReadOnly::GetKeyframes(JSContext*&aCx,nsTArray<JSObject*>&aResult,ErrorResult&aRv){MOZ_ASSERT(aResult.IsEmpty());MOZ_ASSERT(!aRv.Failed());if(!aResult.SetCapacity(mKeyframes.Length(),mozilla::fallible)){aRv.Throw(NS_ERROR_OUT_OF_MEMORY);return;}boolisServo=mDocument->IsStyledByServo();for(constKeyframe&keyframe:mKeyframes){// Set up a dictionary object for the explicit membersBaseComputedKeyframekeyframeDict;if(keyframe.mOffset){keyframeDict.mOffset.SetValue(keyframe.mOffset.value());}MOZ_ASSERT(keyframe.mComputedOffset!=Keyframe::kComputedOffsetNotSet,"Invalid computed offset");keyframeDict.mComputedOffset.Construct(keyframe.mComputedOffset);if(keyframe.mTimingFunction){keyframeDict.mEasing.Truncate();keyframe.mTimingFunction.ref().AppendToString(keyframeDict.mEasing);}// else if null, leave easing as its default "linear".if(keyframe.mComposite){keyframeDict.mComposite.Construct(keyframe.mComposite.value());}JS::Rooted<JS::Value>keyframeJSValue(aCx);if(!ToJSValue(aCx,keyframeDict,&keyframeJSValue)){aRv.Throw(NS_ERROR_FAILURE);return;}JS::Rooted<JSObject*>keyframeObject(aCx,&keyframeJSValue.toObject());for(constPropertyValuePair&propertyValue:keyframe.mPropertyValues){nsAutoStringstringValue;if(isServo){if(propertyValue.mServoDeclarationBlock){Servo_DeclarationBlock_SerializeOneValue(propertyValue.mServoDeclarationBlock,propertyValue.mProperty,&stringValue);}else{RawServoAnimationValue*value=mBaseStyleValuesForServo.GetWeak(propertyValue.mProperty);if(value){Servo_AnimationValue_Serialize(value,propertyValue.mProperty,&stringValue);}}}elseif(nsCSSProps::IsShorthand(propertyValue.mProperty)){// nsCSSValue::AppendToString does not accept shorthands properties but// works with token stream values if we pass eCSSProperty_UNKNOWN as// the property.propertyValue.mValue.AppendToString(eCSSProperty_UNKNOWN,stringValue,nsCSSValue::eNormalized);}else{nsCSSValuecssValue=propertyValue.mValue;if(cssValue.GetUnit()==eCSSUnit_Null){// We use an uninitialized nsCSSValue to represent the// "neutral value". We currently only do this for keyframes generated// from CSS animations with missing 0%/100% keyframes. Furthermore,// currently (at least until bug 1339334) keyframes generated from// CSS animations only contain longhand properties so we only need to// handle null nsCSSValues for longhand properties.DebugOnly<bool>uncomputeResult=StyleAnimationValue::UncomputeValue(propertyValue.mProperty,Move(BaseStyle(propertyValue.mProperty).mGecko),cssValue);MOZ_ASSERT(uncomputeResult,"Unable to get specified value from computed value");MOZ_ASSERT(cssValue.GetUnit()!=eCSSUnit_Null,"Got null computed value");}cssValue.AppendToString(propertyValue.mProperty,stringValue,nsCSSValue::eNormalized);}constchar*name=nsCSSProps::PropertyIDLName(propertyValue.mProperty);JS::Rooted<JS::Value>value(aCx);if(!ToJSValue(aCx,stringValue,&value)||!JS_DefineProperty(aCx,keyframeObject,name,value,JSPROP_ENUMERATE)){aRv.Throw(NS_ERROR_FAILURE);return;}}aResult.AppendElement(keyframeObject);}}/* static */constTimeDurationKeyframeEffectReadOnly::OverflowRegionRefreshInterval(){// The amount of time we can wait between updating throttled animations// on the main thread that influence the overflow region.staticconstTimeDurationkOverflowRegionRefreshInterval=TimeDuration::FromMilliseconds(200);returnkOverflowRegionRefreshInterval;}boolKeyframeEffectReadOnly::CanThrottle()const{// Unthrottle if we are not in effect or current. This will be the case when// our owning animation has finished, is idle, or when we are in the delay// phase (but without a backwards fill). In each case the computed progress// value produced on each tick will be the same so we will skip requesting// unnecessary restyles in NotifyAnimationTimingUpdated. Any calls we *do* get// here will be because of a change in state (e.g. we are newly finished or// newly no longer in effect) in which case we shouldn't throttle the sample.if(!IsInEffect()||!IsCurrent()){returnfalse;}nsIFrame*frame=GetAnimationFrame();if(!frame){// There are two possible cases here.// a) No target element// b) The target element has no frame, e.g. because it is in a display:none// subtree.// In either case we can throttle the animation because there is no// need to update on the main thread.returntrue;}// We can throttle the animation if the animation is paint only and// the target frame is out of view or the document is in background tabs.if(CanIgnoreIfNotVisible()){nsIPresShell*presShell=GetPresShell();if((presShell&&!presShell->IsActive())||frame->IsScrolledOutOfView()){returntrue;}}// First we need to check layer generation and transform overflow// prior to the property.mIsRunningOnCompositor check because we should// occasionally unthrottle these animations even if the animations are// already running on compositor.for(constLayerAnimationInfo::Record&record:LayerAnimationInfo::sRecords){// Skip properties that are overridden by !important rules.// (GetEffectiveAnimationOfProperty, as called by// HasEffectiveAnimationOfProperty, only returns a property which is// neither overridden by !important rules nor overridden by other// animation.)if(!HasEffectiveAnimationOfProperty(record.mProperty)){continue;}EffectSet*effectSet=EffectSet::GetEffectSet(mTarget->mElement,mTarget->mPseudoType);MOZ_ASSERT(effectSet,"CanThrottle should be called on an effect ""associated with a target element");layers::Layer*layer=FrameLayerBuilder::GetDedicatedLayer(frame,record.mLayerType);// Unthrottle if the layer needs to be brought up to dateif(!layer||effectSet->GetAnimationGeneration()!=layer->GetAnimationGeneration()){returnfalse;}// If this is a transform animation that affects the overflow region,// we should unthrottle the animation periodically.if(record.mProperty==eCSSProperty_transform&&!CanThrottleTransformChanges(*frame)){returnfalse;}}for(constAnimationProperty&property:mProperties){if(!property.mIsRunningOnCompositor){returnfalse;}}returntrue;}boolKeyframeEffectReadOnly::CanThrottleTransformChanges(nsIFrame&aFrame)const{// If we know that the animation cannot cause overflow,// we can just disable flushes for this animation.// If we don't show scrollbars, we don't care about overflow.if(LookAndFeel::GetInt(LookAndFeel::eIntID_ShowHideScrollbars)==0){returntrue;}TimeStampnow=aFrame.PresContext()->RefreshDriver()->MostRecentRefresh();EffectSet*effectSet=EffectSet::GetEffectSet(mTarget->mElement,mTarget->mPseudoType);MOZ_ASSERT(effectSet,"CanThrottleTransformChanges is expected to be called"" on an effect in an effect set");MOZ_ASSERT(mAnimation,"CanThrottleTransformChanges is expected to be called"" on an effect with a parent animation");TimeStamplastSyncTime=effectSet->LastTransformSyncTime();// If this animation can cause overflow, we can throttle some of the ticks.if(!lastSyncTime.IsNull()&&(now-lastSyncTime)<OverflowRegionRefreshInterval()){returntrue;}// If the nearest scrollable ancestor has overflow:hidden,// we don't care about overflow.nsIScrollableFrame*scrollable=nsLayoutUtils::GetNearestScrollableFrame(&aFrame);if(!scrollable){returntrue;}ScrollbarStylesss=scrollable->GetScrollbarStyles();if(ss.mVertical==NS_STYLE_OVERFLOW_HIDDEN&&ss.mHorizontal==NS_STYLE_OVERFLOW_HIDDEN&&scrollable->GetLogicalScrollPosition()==nsPoint(0,0)){returntrue;}returnfalse;}nsIFrame*KeyframeEffectReadOnly::GetAnimationFrame()const{if(!mTarget){returnnullptr;}nsIFrame*frame;if(mTarget->mPseudoType==CSSPseudoElementType::before){frame=nsLayoutUtils::GetBeforeFrame(mTarget->mElement);}elseif(mTarget->mPseudoType==CSSPseudoElementType::after){frame=nsLayoutUtils::GetAfterFrame(mTarget->mElement);}else{frame=mTarget->mElement->GetPrimaryFrame();MOZ_ASSERT(mTarget->mPseudoType==CSSPseudoElementType::NotPseudo,"unknown mTarget->mPseudoType");}if(!frame){returnnullptr;}returnnsLayoutUtils::GetStyleFrame(frame);}nsIDocument*KeyframeEffectReadOnly::GetRenderedDocument()const{if(!mTarget){returnnullptr;}returnmTarget->mElement->GetComposedDoc();}nsIPresShell*KeyframeEffectReadOnly::GetPresShell()const{nsIDocument*doc=GetRenderedDocument();if(!doc){returnnullptr;}returndoc->GetShell();}/* static */boolKeyframeEffectReadOnly::IsGeometricProperty(constnsCSSPropertyIDaProperty){MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty),"Property should be a longhand property");switch(aProperty){caseeCSSProperty_bottom:caseeCSSProperty_height:caseeCSSProperty_left:caseeCSSProperty_margin_bottom:caseeCSSProperty_margin_left:caseeCSSProperty_margin_right:caseeCSSProperty_margin_top:caseeCSSProperty_padding_bottom:caseeCSSProperty_padding_left:caseeCSSProperty_padding_right:caseeCSSProperty_padding_top:caseeCSSProperty_right:caseeCSSProperty_top:caseeCSSProperty_width:returntrue;default:returnfalse;}}/* static */boolKeyframeEffectReadOnly::CanAnimateTransformOnCompositor(constnsIFrame*aFrame,AnimationPerformanceWarning::Type&aPerformanceWarning){// Disallow OMTA for preserve-3d transform. Note that we check the style property// rather than Extend3DContext() since that can recurse back into this function// via HasOpacity(). See bug 779598.if(aFrame->Combines3DTransformWithAncestors()||aFrame->StyleDisplay()->mTransformStyle==NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D){aPerformanceWarning=AnimationPerformanceWarning::Type::TransformPreserve3D;returnfalse;}// Note that testing BackfaceIsHidden() is not a sufficient test for// what we need for animating backface-visibility correctly if we// remove the above test for Extend3DContext(); that would require// looking at backface-visibility on descendants as well. See bug 1186204.if(aFrame->BackfaceIsHidden()){aPerformanceWarning=AnimationPerformanceWarning::Type::TransformBackfaceVisibilityHidden;returnfalse;}// Async 'transform' animations of aFrames with SVG transforms is not// supported. See bug 779599.if(aFrame->IsSVGTransformed()){aPerformanceWarning=AnimationPerformanceWarning::Type::TransformSVG;returnfalse;}returntrue;}boolKeyframeEffectReadOnly::ShouldBlockAsyncTransformAnimations(constnsIFrame*aFrame,AnimationPerformanceWarning::Type&aPerformanceWarning)const{EffectSet*effectSet=EffectSet::GetEffectSet(mTarget->mElement,mTarget->mPseudoType);for(constAnimationProperty&property:mProperties){// If there is a property for animations level that is overridden by// !important rules, it should not block other animations from running// on the compositor.// NOTE: We don't currently check for !important rules for properties that// don't run on the compositor. As result such properties (e.g. margin-left)// can still block async animations even if they are overridden by// !important rules.if(effectSet&&effectSet->PropertiesWithImportantRules().HasProperty(property.mProperty)&&effectSet->PropertiesForAnimationsLevel().HasProperty(property.mProperty)){continue;}// Check for geometric propertiesif(IsGeometricProperty(property.mProperty)){aPerformanceWarning=AnimationPerformanceWarning::Type::TransformWithGeometricProperties;returntrue;}// Check for unsupported transform animationsif(property.mProperty==eCSSProperty_transform){if(!CanAnimateTransformOnCompositor(aFrame,aPerformanceWarning)){returntrue;}}}returnfalse;}boolKeyframeEffectReadOnly::HasGeometricProperties()const{for(constAnimationProperty&property:mProperties){if(IsGeometricProperty(property.mProperty)){returntrue;}}returnfalse;}voidKeyframeEffectReadOnly::SetPerformanceWarning(nsCSSPropertyIDaProperty,constAnimationPerformanceWarning&aWarning){if(aWarning.mType==AnimationPerformanceWarning::Type::ContentTooLarge&&!mRecordedContentTooLarge){// ContentTooLarge stores: frameSize (w x h),// relativeLimit (w x h), i.e. =~ viewport size *// ratioLimit// absoluteLimit (w x h)MOZ_ASSERT(aWarning.mParams&&aWarning.mParams->Length()>=4,"ContentTooLarge warning should have at least 4 parameters");constnsTArray<int32_t>¶ms=aWarning.mParams.ref();uint32_tframeSize=uint32_t(params[0])*params[1];floatviewportRatioX=gfxPrefs::AnimationPrerenderViewportRatioLimitX();floatviewportRatioY=gfxPrefs::AnimationPrerenderViewportRatioLimitY();doubleviewportWidth=viewportRatioX?params[2]/viewportRatioX:params[2];doubleviewportHeight=viewportRatioY?params[3]/viewportRatioY:params[3];doubleviewportSize=viewportWidth*viewportHeight;uint32_tframeToViewport=frameSize/viewportSize*100.0;Telemetry::Accumulate(Telemetry::ASYNC_ANIMATION_CONTENT_TOO_LARGE_FRAME_SIZE,frameSize);Telemetry::Accumulate(Telemetry::ASYNC_ANIMATION_CONTENT_TOO_LARGE_PERCENTAGE,frameToViewport);mRecordedContentTooLarge=true;}for(AnimationProperty&property:mProperties){if(property.mProperty==aProperty&&(!property.mPerformanceWarning||*property.mPerformanceWarning!=aWarning)){property.mPerformanceWarning=Some(aWarning);nsXPIDLStringlocalizedString;if(nsLayoutUtils::IsAnimationLoggingEnabled()&&property.mPerformanceWarning->ToLocalizedString(localizedString)){nsAutoCStringlogMessage=NS_ConvertUTF16toUTF8(localizedString);AnimationUtils::LogAsyncAnimationFailure(logMessage,mTarget->mElement);}return;}}}voidKeyframeEffectReadOnly::RecordFrameSizeTelemetry(uint32_taPixelArea){if(!mRecordedFrameSize){Telemetry::Accumulate(Telemetry::ASYNC_ANIMATION_FRAME_SIZE,aPixelArea);mRecordedFrameSize=true;}}staticalready_AddRefed<nsStyleContext>CreateStyleContextForAnimationValue(nsCSSPropertyIDaProperty,constStyleAnimationValue&aValue,nsStyleContext*aBaseStyleContext){MOZ_ASSERT(aBaseStyleContext,"CreateStyleContextForAnimationValue needs to be called ""with a valid nsStyleContext");RefPtr<AnimValuesStyleRule>styleRule=newAnimValuesStyleRule();styleRule->AddValue(aProperty,aValue);nsCOMArray<nsIStyleRule>rules;rules.AppendObject(styleRule);MOZ_ASSERT(aBaseStyleContext->PresContext()->StyleSet()->IsGecko(),"ServoStyleSet should not use StyleAnimationValue for animations");nsStyleSet*styleSet=aBaseStyleContext->PresContext()->StyleSet()->AsGecko();RefPtr<nsStyleContext>styleContext=styleSet->ResolveStyleByAddingRules(aBaseStyleContext,rules);// We need to call StyleData to generate cached data for the style context.// Otherwise CalcStyleDifference returns no meaningful result.styleContext->AsGecko()->StyleData(nsCSSProps::kSIDTable[aProperty]);returnstyleContext.forget();}voidKeyframeEffectReadOnly::CalculateCumulativeChangeHint(nsStyleContext*aStyleContext){if(mDocument->IsStyledByServo()){// FIXME (bug 1303235): Do this for Servo tooreturn;}mCumulativeChangeHint=nsChangeHint(0);for(constAnimationProperty&property:mProperties){for(constAnimationPropertySegment&segment:property.mSegments){// In case composite operation is not 'replace' or value is null,// we can't throttle animations which will not cause any layout changes// on invisible elements because we can't calculate the change hint for// such properties until we compose it.if(!segment.HasReplaceableValues()){mCumulativeChangeHint=~nsChangeHint_Hints_CanIgnoreIfNotVisible;return;}RefPtr<nsStyleContext>fromContext=CreateStyleContextForAnimationValue(property.mProperty,segment.mFromValue.mGecko,aStyleContext);RefPtr<nsStyleContext>toContext=CreateStyleContextForAnimationValue(property.mProperty,segment.mToValue.mGecko,aStyleContext);uint32_tequalStructs=0;uint32_tsamePointerStructs=0;nsChangeHintchangeHint=fromContext->CalcStyleDifference(toContext,&equalStructs,&samePointerStructs);mCumulativeChangeHint|=changeHint;}}}voidKeyframeEffectReadOnly::SetAnimation(Animation*aAnimation){if(mAnimation==aAnimation){return;}// Restyle for the old animation.RequestRestyle(EffectCompositor::RestyleType::Layer);mAnimation=aAnimation;// The order of these function calls is important:// NotifyAnimationTimingUpdated() need the updated mIsRelevant flag to check// if it should create the effectSet or not, and MarkCascadeNeedsUpdate()// needs a valid effectSet, so we should call them in this order.if(mAnimation){mAnimation->UpdateRelevance();}NotifyAnimationTimingUpdated();if(mAnimation){MarkCascadeNeedsUpdate();}}boolKeyframeEffectReadOnly::CanIgnoreIfNotVisible()const{if(!AnimationUtils::IsOffscreenThrottlingEnabled()){returnfalse;}// FIXME (bug 1303235): We don't calculate mCumulativeChangeHint for// the Servo backend yetif(mDocument->IsStyledByServo()){returnfalse;}// FIXME: For further sophisticated optimization we need to check// change hint on the segment corresponding to computedTiming.progress.returnNS_IsHintSubset(mCumulativeChangeHint,nsChangeHint_Hints_CanIgnoreIfNotVisible);}voidKeyframeEffectReadOnly::MaybeUpdateFrameForCompositor(){nsIFrame*frame=GetAnimationFrame();if(!frame){return;}// FIXME: Bug 1272495: If this effect does not win in the cascade, the// NS_FRAME_MAY_BE_TRANSFORMED flag should be removed when the animation// will be removed from effect set or the transform keyframes are removed// by setKeyframes. The latter case will be hard to solve though.for(constAnimationProperty&property:mProperties){if(property.mProperty==eCSSProperty_transform){frame->AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);return;}}}voidKeyframeEffectReadOnly::MarkCascadeNeedsUpdate(){if(!mTarget){return;}EffectSet*effectSet=EffectSet::GetEffectSet(mTarget->mElement,mTarget->mPseudoType);if(!effectSet){return;}effectSet->MarkCascadeNeedsUpdate();}boolKeyframeEffectReadOnly::HasComputedTimingChanged()const{// Typically we don't need to request a restyle if the progress hasn't// changed since the last call to ComposeStyle. The one exception is if the// iteration composite mode is 'accumulate' and the current iteration has// changed, since that will often produce a different result.ComputedTimingcomputedTiming=GetComputedTiming();returncomputedTiming.mProgress!=mProgressOnLastCompose||(mEffectOptions.mIterationComposite==IterationCompositeOperation::Accumulate&&computedTiming.mCurrentIteration!=mCurrentIterationOnLastCompose);}boolKeyframeEffectReadOnly::ContainsAnimatedScale(constnsIFrame*aFrame)const{if(!IsCurrent()){returnfalse;}for(constAnimationProperty&prop:mProperties){if(prop.mProperty!=eCSSProperty_transform){continue;}AnimationValuebaseStyle=BaseStyle(prop.mProperty);if(baseStyle.IsNull()){// If we failed to get the base style, we consider it has scale value// here just to be safe.returntrue;}gfxSizesize=baseStyle.GetScaleValue(aFrame);if(size!=gfxSize(1.0f,1.0f)){returntrue;}// This is actually overestimate because there are some cases that combining// the base value and from/to value produces 1:1 scale. But it doesn't// really matter.for(constAnimationPropertySegment&segment:prop.mSegments){if(!segment.mFromValue.IsNull()){gfxSizefrom=segment.mFromValue.GetScaleValue(aFrame);if(from!=gfxSize(1.0f,1.0f)){returntrue;}}if(!segment.mToValue.IsNull()){gfxSizeto=segment.mToValue.GetScaleValue(aFrame);if(to!=gfxSize(1.0f,1.0f)){returntrue;}}}}returnfalse;}voidKeyframeEffectReadOnly::UpdateEffectSet(EffectSet*aEffectSet)const{EffectSet*effectSet=aEffectSet?aEffectSet:EffectSet::GetEffectSet(mTarget->mElement,mTarget->mPseudoType);if(!effectSet){return;}if(HasAnimationOfProperty(eCSSProperty_opacity)){effectSet->SetMayHaveOpacityAnimation();}if(HasAnimationOfProperty(eCSSProperty_transform)){effectSet->SetMayHaveTransformAnimation();}}templatevoidKeyframeEffectReadOnly::ComposeStyle<RefPtr<AnimValuesStyleRule>&>(RefPtr<AnimValuesStyleRule>&aAnimationRule,constnsCSSPropertyIDSet&aPropertiesToSkip);templatevoidKeyframeEffectReadOnly::ComposeStyle<RawServoAnimationValueMap&>(RawServoAnimationValueMap&aAnimationValues,constnsCSSPropertyIDSet&aPropertiesToSkip);}// namespace dom}// namespace mozilla